/*
 * GPUC
 * 
 * Copyright © 2008 Attila T. Áfra <attila.afra@gmail.com>
 * 
 * This program is free software: you can redistribute it and/or modify it under the terms of the
 * GNU General Public License as published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along with this program. If
 * not, see <http://www.gnu.org/licenses/>.
 */

#include <stdlib.h>
#include "StreamPrivate.h"

/*------------------------------------------------------------------------------------------------*/

GPUC_API GPUC_Stream
GPUC_Stream_create(int       width,
                   int       height,
                   GPUC_Type elementType)
{
    GPUC_Stream stream;
    GPUC_Bool   typeSupported;

    if (!GPUC_initialized)
        return NULL;

    if ((width <= 0) || (height <= 0))
    {
        GPUC_error = GPUC_Error_InvalidArgument;
        return 0;
    }

    typeSupported = GPUC_Stream_isFormatSupported(elementType);
    
    if (GPUC_error == GPUC_Error_None)
    {
        if (!typeSupported)
        {
            GPUC_error = GPUC_Error_UnsupportedStreamType;
            return NULL;
        }
    }
    else
    {
        GPUC_error = GPUC_Error_InvalidArgument;
        return NULL;
    }

    if ((width > GPUC_maxTextureRectSize) || (height > GPUC_maxTextureRectSize))
    {
        GPUC_error = GPUC_Error_UnsupportedStreamSize;
        return NULL;
    }

    // Allocate memory for the stream object
    stream = (GPUC_Stream)malloc(sizeof (struct GPUC_Stream_Object));

    if (stream == NULL)
    {
        GPUC_error = GPUC_Error_OutOfMemory;
        return NULL;
    }

    // Create the texture
    glGenTextures(1, &stream->textureId);
    glBindTexture(GL_TEXTURE_RECTANGLE_ARB, stream->textureId);

    glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_S, GL_CLAMP);
    glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_T, GL_CLAMP);

    switch (elementType)
    {
    case GPUC_Type_Float:
        stream->textureFormat         = GL_LUMINANCE;
        stream->textureInternalFormat = GL_LUMINANCE32F_ARB;
        stream->textureType           = GL_FLOAT;
        stream->elementSize           = 4;
        break;

    case GPUC_Type_Float2:
        stream->textureFormat         = GL_LUMINANCE_ALPHA;
        stream->textureInternalFormat = GL_LUMINANCE_ALPHA32F_ARB;
        stream->textureType           = GL_FLOAT;
        stream->elementSize           = 8;
        break;

    case GPUC_Type_Float3:
        stream->textureFormat         = GL_RGB;
        stream->textureInternalFormat = GL_RGB32F_ARB;
        stream->textureType           = GL_FLOAT;
        stream->elementSize           = 12;
        break;

    case GPUC_Type_Float4:
        stream->textureFormat         = GL_RGBA;
        stream->textureInternalFormat = GL_RGBA32F_ARB;
        stream->textureType           = GL_FLOAT;
        stream->elementSize           = 16;
        break;

    case GPUC_Type_Half:
        stream->textureFormat         = GL_LUMINANCE;
        stream->textureInternalFormat = GL_LUMINANCE16F_ARB;
        stream->textureType           = GL_HALF_FLOAT_ARB;      
        stream->elementSize           = 2;
        break;

    case GPUC_Type_Half2:
        stream->textureFormat         = GL_LUMINANCE_ALPHA;
        stream->textureInternalFormat = GL_LUMINANCE_ALPHA16F_ARB;
        stream->textureType           = GL_HALF_FLOAT_ARB;
        stream->elementSize           = 4;
        break;

    case GPUC_Type_Half3:
        stream->textureFormat         = GL_RGB;
        stream->textureInternalFormat = GL_RGB16F_ARB;
        stream->textureType           = GL_HALF_FLOAT_ARB;
        stream->elementSize           = 6;
        break;

    case GPUC_Type_Half4:
        stream->textureFormat         = GL_RGBA;
        stream->textureInternalFormat = GL_RGBA16F_ARB;
        stream->textureType           = GL_HALF_FLOAT_ARB;
        stream->elementSize           = 8;
        break;

    case GPUC_Type_Byte:
        stream->textureFormat         = GL_LUMINANCE;
        stream->textureInternalFormat = GL_LUMINANCE8;
        stream->textureType           = GL_UNSIGNED_BYTE;
        stream->elementSize           = 1;
        break;

    case GPUC_Type_Byte2:
        stream->textureFormat         = GL_LUMINANCE_ALPHA;
        stream->textureInternalFormat = GL_LUMINANCE8_ALPHA8;
        stream->textureType           = GL_UNSIGNED_BYTE;
        stream->elementSize           = 2;
        break;

    case GPUC_Type_Byte3:
        stream->textureFormat         = GL_RGB;
        stream->textureInternalFormat = GL_RGB8;
        stream->textureType           = GL_UNSIGNED_BYTE;
        stream->elementSize           = 3;
        break;

    case GPUC_Type_Byte4:
        stream->textureFormat         = GL_BGRA;
        stream->textureInternalFormat = GL_RGBA8;
        stream->textureType           = GL_UNSIGNED_BYTE;
        stream->elementSize           = 4;
        break;
        
    default:
        // This should never happen!
        ;
    }

    glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, stream->textureInternalFormat, (GLsizei)width,
                 (GLsizei)height, 0, stream->textureFormat, stream->textureType, NULL);

    stream->width       = width;
    stream->height      = height;
    stream->elementType = elementType;
    stream->size        = width * height * stream->elementSize;

    // Success
    stream->referenceCount = 1;
    ++GPUC_objectCount;
    GPUC_error = GPUC_Error_None;
    return stream;
}

/*------------------------------------------------------------------------------------------------*/

GPUC_API void
GPUC_Stream_destroy(GPUC_Stream stream)
{
    GPUC_error = GPUC_Error_None;

    if (--stream->referenceCount == 0)
    {
        glDeleteTextures(1, &stream->textureId);

        free(stream);
        --GPUC_objectCount;
    }
}

/*------------------------------------------------------------------------------------------------*/

GPUC_API GPUC_Bool
GPUC_Stream_isFormatSupported(GPUC_Type elementType)
{
    GPUC_Bool supported;

    if (!GPUC_initialized)
        return 0;

    switch (elementType)
    {
    case GPUC_Type_Float:
    case GPUC_Type_Float2:
    case GPUC_Type_Float3:
    case GPUC_Type_Float4:
        supported = GLEW_ARB_texture_float;
        break;
        
    case GPUC_Type_Half:
    case GPUC_Type_Half2:
    case GPUC_Type_Half3:
    case GPUC_Type_Half4:
        supported = GLEW_ARB_texture_float && GLEW_ARB_half_float_pixel;
        break;

    case GPUC_Type_Byte:
    case GPUC_Type_Byte2:
    case GPUC_Type_Byte3:
    case GPUC_Type_Byte4:
        supported = 1;
        break;

    default:
        GPUC_error = GPUC_Error_InvalidArgument;
        return 0;
    }

    GPUC_error = GPUC_Error_None;
    return supported;
}

/*------------------------------------------------------------------------------------------------*/

GPUC_API int
GPUC_Stream_getMaxDimension()
{
    if (!GPUC_initialized)
        return -1;

    GPUC_error = GPUC_Error_None;
    return GPUC_maxTextureRectSize;
}

/*------------------------------------------------------------------------------------------------*/

GPUC_API int
GPUC_Stream_getWidth(GPUC_Stream stream)
{
    GPUC_error = GPUC_Error_None;
    return stream->width;
}

/*------------------------------------------------------------------------------------------------*/

GPUC_API int
GPUC_Stream_getHeight(GPUC_Stream stream)
{
    GPUC_error = GPUC_Error_None;
    return stream->height;
}

/*------------------------------------------------------------------------------------------------*/

GPUC_API GPUC_Type
GPUC_Stream_getElementType(GPUC_Stream stream)
{
    GPUC_error = GPUC_Error_None;
    return stream->elementType;
}

/*------------------------------------------------------------------------------------------------*/

GPUC_API size_t
GPUC_Stream_getSize(GPUC_Stream stream)
{
    GPUC_error = GPUC_Error_None;
    return stream->size;
}

/*------------------------------------------------------------------------------------------------*/
